package csci6450.chickenFriedFS.nuggetClient;

import csci6450.chickenFriedFS.nuggetCore.ThreadStatus;
import csci6450.chickenFriedFS.nuggetClient.threads.functions.GetNuggetFromServer;
import csci6450.chickenFriedFS.nuggetClient.threads.functions.PutNuggetOnServer;
import csci6450.chickenFriedFS.nuggetClient.threads.functions.StopMetaServer;
import csci6450.chickenFriedFS.nuggetClient.threads.functions.TimestampMetaServer;
import csci6450.chickenFriedFS.nuggetCore.ChickenFile;
import csci6450.chickenFriedFS.nuggetCore.Nugget;
import csci6450.chickenFriedFS.nuggetCore.NuggetConstants;
import csci6450.chickenFriedFS.nuggetCore.NuggetServerInfo;
import csci6450.chickenFriedFS.nuggetCore.NuggetUtilities;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class NuggetClient {

    private static String MACHINE_NAME;

    static {
        try {
            MACHINE_NAME = InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException ex) {
            Logger.getLogger(NuggetClient.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static boolean getFile(String fileName, String saveTo) throws UnknownHostException, IOException, ClassNotFoundException {
        ChickenFile response = null;
        Socket socket = null;
        try {
            // open a socket connection
            socket = new Socket(RuntimeSettings.getInstance().getMetaServerIP(), RuntimeSettings.getInstance().getMetaServerPort());
            // open I/O streams for objects
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            outputStream.writeObject(NuggetConstants.GET + " " + fileName);
            outputStream.flush();
            response = (ChickenFile) inputStream.readObject();
        } catch (IOException iOException) {
            throw iOException;
        } catch (ClassNotFoundException classNotFoundException) {
            throw classNotFoundException;
        } finally {
            socket.close();
        }

        if (response != null) {
            int numNuggets = response.getFileNuggetCount() - 1; //lol
            int bufferStart = response.getNuggets().get(numNuggets).getBufferStart();
            long fileSize = (numNuggets * NuggetConstants.CHUNK_SIZE) + bufferStart;

            //create the file
            File create = new File(saveTo);
            if (create.exists()) {
                create.delete();
            }
            try {
                create.getParentFile().mkdirs();
            } catch (Exception e) {
            }

            create.createNewFile();
            System.out.println("Getting filesize :=" + fileSize);
//            RandomAccessFile f = new RandomAccessFile(create, "rw");
//            f.setLength(fileSize);
//            f.close();

            //means there is one
            //now go get the chunks and put them back together
            ArrayList<ThreadStatus> statusList = new ArrayList<ThreadStatus>();
            for (int i = 0; i < response.getNuggets().size(); i = i + 1) {
                ThreadStatus status = new ThreadStatus(response.getNuggets().get(i).getNuggetIdentifier());
                GetNuggetFromServer get = new GetNuggetFromServer(response.getNuggets().get(i), status, saveTo);
                statusList.add(status);
                get.start();
            }

            //wait for them to finish
            //this is not working
            boolean done = false;
            boolean success = true;
            while (!done) {
                done = true;
                success = true;
                for (int i = 0; i < statusList.size(); i = i + 1) {
                    done = done && statusList.get(i).isFinished();
                    success = success && statusList.get(i).isSuccessful();
                }
            }

            //done with statusList
            statusList.clear();
            if (!success) {
                File file = new File(saveTo);
                file.delete();
                System.out.println("Sorry we burned your chicken!");
                return false;
            }
            File h = new File(fileName);
            System.out.println("File created is: " + h.getTotalSpace());
            return true;
        } else {
            System.out.println("File not found.");
            return false;
        }
    }

    public static boolean addFile(File f) throws UnknownHostException, IOException, ClassNotFoundException {
        boolean success = false;
        String filename = f.getName();

        //get what servers and how many chunks to put on each server
        long nuggetCount = (f.length() / (int) NuggetConstants.CHUNK_SIZE);
        if (f.length() % (NuggetConstants.CHUNK_SIZE) != 0) {
            nuggetCount = nuggetCount + 1;
        }
        System.out.println("Number of nuggets is: " + nuggetCount);
        int bufferStart = (int) (f.length() % NuggetConstants.CHUNK_SIZE);
        // open a socket connection
        List<NuggetServerInfo> servers = null;
        Socket socket = null;
        try {
            socket = new Socket(RuntimeSettings.getInstance().getMetaServerIP(), RuntimeSettings.getInstance().getMetaServerPort());
            // open I/O streams for objects
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            outputStream.writeObject(NuggetConstants.PUT + " " + nuggetCount);
            outputStream.flush();
            Object response = inputStream.readObject();
            servers = null;
            if (response instanceof String) {
                String str = (String) response;
            } else {
                servers = (List<NuggetServerInfo>) response;
            }
        } catch (IOException iOException) {
            iOException.printStackTrace();
            throw iOException;
        } catch (ClassNotFoundException classNotFoundException) {
            classNotFoundException.printStackTrace();
            throw classNotFoundException;
        } finally {
            socket.close();
        }
        if (servers != null) {
            String ipAddress = null;
            int port = -1;
            //break file up and start sending the files
            int k = 0;
            ArrayList<ThreadStatus> statusList = new ArrayList<ThreadStatus>();
            for (long i = 0; i < f.length(); i = i + NuggetConstants.CHUNK_SIZE) {
                NuggetUtilities.memCheck();
                RandomAccessFile read = new RandomAccessFile(f, "r");
                read.seek(i);
                byte[] result = new byte[(int) NuggetConstants.CHUNK_SIZE];
                read.read(result);
                read.close();
                System.out.println("Nugget going to " + servers.get(k).getIpAddress() + ":" + servers.get(k).getClientPort());
                ipAddress = servers.get(k).getIpAddress();
                port = servers.get(k).getClientPort();
                //put it in a nugget
                Nugget nugget = new Nugget(filename, (int) (i / NuggetConstants.CHUNK_SIZE),
                        (int) nuggetCount, NuggetClient.buildNextNuggetIdentifier(),
                        ipAddress, port);
                //need to know the status
                nugget.setNugget(result);
                if ((k + 1) == nuggetCount) {
                    nugget.setBufferStart(bufferStart);
                }
                statusList.add(new ThreadStatus(nugget.getNuggetIdentifier()));
                //create thread
                PutNuggetOnServer put = new PutNuggetOnServer(nugget, statusList.get(statusList.size() - 1));
                //start thread
                put.start();
                k = k + 1;
            }
            //wait for them to finish
            //this is not working

            boolean done = false;
            while (!done) {
                done = true;
                success = true;
                for (int i = 0; i < statusList.size(); i = i + 1) {
                    done = done && statusList.get(i).isFinished();
                    success = success && statusList.get(i).isSuccessful();
                }
            }
            statusList.clear();
        }
        if (!success) {
            try {
                socket = new Socket(RuntimeSettings.getInstance().getMetaServerIP(), RuntimeSettings.getInstance().getMetaServerPort());
                // open I/O streams for objects
                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                outputStream.writeObject(NuggetConstants.FAILURE + " " + filename);
                outputStream.flush();
                String response = (String) inputStream.readObject();
                System.out.println(response);
            } catch (IOException iOException) {
                throw iOException;
            } catch (ClassNotFoundException classNotFoundException) {
                throw classNotFoundException;
            } finally {
                socket.close();
            }
        }
        return success;
    }

    public static ArrayList<String> getFileNameList() throws UnknownHostException, IOException, ClassNotFoundException {
        Socket socket = null;
        try {
            socket = new Socket(RuntimeSettings.getInstance().getMetaServerIP(), RuntimeSettings.getInstance().getMetaServerPort());
            // open I/O streams for objects
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            outputStream.writeObject(NuggetConstants.LS);
            outputStream.flush();
            List<ChickenFile> response = (List<ChickenFile>) inputStream.readObject();
            socket.close();

            ArrayList<String> files = new ArrayList<String>();
            for (int i = 0; i < response.size(); i = i + 1) {
                files.add(response.get(i).getFileName());
            }

            return files;
        } catch (ClassNotFoundException ex) {
            throw ex;
        } catch (UnknownHostException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (socket != null) {
                socket.close();
            }
        }
    }

    public static ArrayList<NuggetServerInfo> getNuggetServers() {
        return null;
    }

    public static boolean format() throws IOException, ClassNotFoundException {
        Socket socket = null;
        try {
            socket = new Socket(RuntimeSettings.getInstance().getMetaServerIP(), RuntimeSettings.getInstance().getMetaServerPort());
            // open I/O streams for objects
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            outputStream.writeObject(NuggetConstants.FORMAT);
            outputStream.flush();
            String response = (String) inputStream.readObject();
            return response.equals(NuggetConstants.SUCCESS);
        } catch (ClassNotFoundException ex) {
            throw ex;
        } catch (UnknownHostException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (socket != null) {
                socket.close();
            }
        }
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.70E9A041-9A7C-6AEE-1771-76A0E4978304]
    // </editor-fold> 
    public static boolean removeFile(String fileName) throws UnknownHostException, IOException, ClassNotFoundException {
        Socket socket = null;
        try {
            socket = new Socket(RuntimeSettings.getInstance().getMetaServerIP(), RuntimeSettings.getInstance().getMetaServerPort());
            // open I/O streams for objects
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            outputStream.writeObject(NuggetConstants.RM + " " + fileName);
            outputStream.flush();
            String response = (String) inputStream.readObject();
            socket.close();
            return response.equals(NuggetConstants.SUCCESS);
        } catch (ClassNotFoundException ex) {
            throw ex;
        } catch (UnknownHostException ex) {
            throw ex;
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (socket != null) {
                socket.close();
            }
        }
    }

    /**
     * This will tell the Meta Server to shutdown (i.e. clost). Note: Once called
     * and successfull it will meanyou will have to restart the Meta Server  on the
     * machine itself, since you will not be able to do this using this API.
     * 
     * @return true if successful, and false otherwise.
     */
    public static boolean stopMetaServer() throws UnknownHostException, IOException, ClassNotFoundException {
        StopMetaServer stop = new StopMetaServer();
        return stop.execute();
    }

    public static Date getTimestamp() throws UnknownHostException, IOException, ClassNotFoundException {
        TimestampMetaServer timestamp = new TimestampMetaServer();
        return timestamp.execute();
    }

    public static long getFreeSpace() throws UnknownHostException, IOException, ClassNotFoundException {
        long free = -1;
        Socket socket = null;
        try {
            socket = new Socket(RuntimeSettings.getInstance().getMetaServerIP(), RuntimeSettings.getInstance().getMetaServerPort());
            // open I/O streams for objects
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            outputStream.writeObject(NuggetConstants.DF);
            outputStream.flush();
            String response = (String) inputStream.readObject();
            free = Long.parseLong(response);
        } catch (IOException iOException) {
            throw iOException;
        } catch (ClassNotFoundException classNotFoundException) {
            throw classNotFoundException;
        } finally {
            if (socket != null) {
                socket.close();
            }
        }
        return free;
    }

    private static String buildNextNuggetIdentifier() {
        return MACHINE_NAME + (new Date()).getTime();
    }
}

